﻿#region Using Statements
using System;
using System.Diagnostics;
using DirectZZT.Shared.Core;
using DirectZZT.Shared.World.Objects;
using Microsoft.Xna.Framework;
using DirectZZT.Shared.World;
#endregion

namespace DirectZZT.BoardObjects
{
    /// <summary>
    /// Represents the bullet ("Bullet") board object.
    /// </summary>
    [Export("Bullet")]
    [BoardObject(Identifier = "Bullet", Classification = BoardObjectClassification.Item, AllowBecome = false, Serialize = false)]
    public sealed class BulletBoardObject : BoardObject, IProjectileBoardObject
    {
        #region Constants

        /// <summary>
        /// Defines the default bullet range.
        /// By default, a bullet covers the entire board, but can be shorter or longer.
        /// </summary>
        public const int DefaultBulletRange = Board.BoardWidth;
        /// <summary>
        /// Defines the default bullet speed.
        /// </summary>
        public const float DefaultBulletSpeed = 2.0f;

        #endregion

        #region Fields

        private Stopwatch _sw;
        private int _currentRange;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the BulletBoardObject class.
        /// </summary>
        public BulletBoardObject()
        {
            this.Texture = "Textures/PlayfieldObjects/Bullet";
            this.Enabled = true;
            this.Visible = true;
            _sw = new Stopwatch();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Not even in DirectZZT supported.
        /// </summary>
        /// <param name="featureSet"></param>
        /// <returns></returns>
        protected override bool CanAttachScript(FeatureSet featureSet)
        {
            return false;
        }

        /// <summary>
        /// Called when this object spawns.
        /// </summary>
        /// <param name="modifiers"></param>
        public override void OnSpawn(string[] modifiers)
        {
            base.OnSpawn(modifiers);

            _sw.Start();
        }

        /// <summary>
        /// Updates the bullet and moves it.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            if (_sw.ElapsedMilliseconds - ((10f / Speed) * 12.5f) > 0.0f)
            {

                // handle move
                bool disappearBullet = false;
                BoardObject obj = null;
                int x = this.X;
                int y = this.Y;

                // only do this if the range allows
                if (this.Range <= 0 || _currentRange < this.Range)
                {
                    switch (this.TravelDirection)
                    {
                        case Direction.North: y -= 1; break;
                        case Direction.South: y += 1; break;
                        case Direction.West: x -= 1; break;
                        case Direction.East: x += 1; break;
                    }

                    if (x < 0 || x > DirectZZT.Shared.World.Board.BoardWidth ||
                        y < 0 || y > DirectZZT.Shared.World.Board.BoardHeight)
                    {
                        disappearBullet = true;
                    }
                    else
                    {
                        obj = this.Board.GetObjectAt(x, y);
                        if (obj != null)
                        {
                            // call on hit
                            if (this.CanHit(obj))
                            {
                                disappearBullet = this.OnHit(obj);
                            }
                            else
                            {
                                // disappear bullet!
                                disappearBullet = true;
                            }
                        }
                    }
                }
                else
                {
                    disappearBullet = true;
                }

                if (disappearBullet)
                {
                    // disappear, bullet!
                    DestroyBullet();
                }
                else
                {
                    this.X = x;
                    this.Y = y;
                    //if (this.MoveAbs(x, y))
                    //{
                    //    DestroyBullet();
                    //    return;
                    //}

                    _currentRange++;

                    _sw.Restart();
                }
            }
        }

        private void DestroyBullet()
        {
            _sw.Stop();
            this.Board.DieObject(this);

            Logging.GetLogging().LogFormat(LoggingMessageType.Debug, "Game", "", "Bullet object '{0}' destroyed (hit or exceeded range).", this.Id);
        }

        #endregion

        #region IProjectileBoardObject Members

        /// <summary>
        /// Gets/sets the source object which spawned this projectile.
        /// </summary>
        public BoardObject Source { get; set; }
        /// <summary>
        /// Gets/sets the current speed multiplier of this projectile.
        /// </summary>
        public float Speed { get; set; }
        /// <summary>
        /// Gets/sets the range (in fields) of this projectile. Use 0 for infinite range until hitting something (border or an other object).
        /// </summary>
        public int Range { get; set; }
        /// <summary>
        /// Gets/sets the direction in which the projectile travels until hitting something.
        /// </summary>
        public Direction TravelDirection { get; set; }

        /// <summary>
        /// Determines whether or not the specified <see cref="BoardObject"/> can be hit this projectile item.
        /// Some objects (like a fake wall, another projectile or such) cannot be hit, but most can.
        /// </summary>
        /// <param name="obj">The <see cref="BoardObject"/> that is about being hit.</param>
        /// <returns>Whether or not the specified <see cref="BoardObject"/> can be hit by this projectile.</returns>
        public bool CanHit(BoardObject obj)
        {
            // only 
            return obj is ICreatureBoardObject
                || obj is RicochetBoardObject;
        }

        /// <summary>
        /// Called when the specified <see cref="BoardObject"/> gets hit by this projectile.
        /// </summary>
        /// <param name="obj">The <see cref="BoardObject"/> that gets hit by this projectile.</param>
        /// <returns>Whether or not to disappear the bullet.</returns>
        public bool OnHit(BoardObject obj)
        {
            ICreatureBoardObject creature = obj as ICreatureBoardObject;
            if (creature == null)
            {
                //Logging.GetLogging().LogFormat(LoggingMessageType.Warning, "Game", "", "Can only hit objects of type {0}!", typeof(ICreatureBoardObject).Name);
                // if its no creature, let it collide
                obj.OnCollide(new CollisionArgs() { Object = this });
                return false;
            }

            creature.InflictDamage(10);
            return true;
        }

        #endregion

        #region Factory methods

        /// <summary>
        /// Spawns and fires a bullet with the default bullet parameters into the given direction.
        /// </summary>
        /// <param name="source">The source object. Must not be null.</param>
        /// <param name="direction">The direction into which to shoot the bullet.</param>
        /// <returns>The <see cref="IProjectileBoardObject"/> instance representing the spawned bullet.
        /// -or- <c>null</c>, if the bullet couldn't be spawned.</returns>
        public static IProjectileBoardObject FireBullet(BoardObject source, Direction direction)
        {
            if (direction == Direction.None)
            {
                Logging.GetLogging().Log(LoggingMessageType.Error, "Game", "", "Cannot fire in Direction 'None'!");
                return null;
            }

            BulletBoardObject bullet = new BulletBoardObject();
            bullet.Source = source;
            bullet.TravelDirection = direction;
            // set appropriate position
            bullet.X = source.X + direction.GetDeltaX();
            bullet.Y = source.Y + direction.GetDeltaY();
            // set default parameters
            bullet.Range = DefaultBulletRange;
            bullet.Speed = DefaultBulletSpeed;

            // spawn it
            if (source.Board.SpawnObject(bullet, null))
            {
                return bullet;
            }

            // error spawning
            return null;
        }

        #endregion
    }
}
